<template>
    <div class="chat-input-next-wrap" ref="chatInputNextWrap">
      <div 
        contenteditable 
        :style="{...cusStyle}" 
        class="chat-input-next" 
        ref="chatInputNext"
        :placeholder="placeholder" 
        id="chat-input-next-editor" 
        @paste="handleParse"  
        @input="handleInput"
        @keydown="handleKeyDown"
        :ondragover="handleAllowDrop"
        :ondrop="handleDrop">    
      </div>
      <at-popover
        v-if="!isEmpty(atUserList)"
        ref="atPopoverRef"
        :visible="atPopoverVisible"
        @confirm="handleAtConfirm"
        :list="atStr?searchAtList:atUserList"
      />
    </div>
</template>
<script lang="ts">
export default {
  name: "ChatInputNext",
};
</script>
<script lang="ts" setup>
import { withDefaults,defineExpose,defineEmits,ref,watch } from 'vue';
import { useInput } from "@/hooks/useInput";
import {acceptList,showFileImageList} from  "@/utils/data"
import { throttle,isEmpty } from "lodash";
import PinyinEngine from "pinyin-engine";
import atPopover from './at-popover.vue';
import type {InlineStyles,AtEachMember} from "./types/index"

const props=withDefaults(defineProps<{
  cusStyle?:InlineStyles
  placeholder?:string
  atUserList?:Array<AtEachMember>
}>(),{
  placeholder:"请输入内容",
  atUserList:()=>[]
});

const emits=defineEmits(['error'])

// 插入文件
const insertFile=(file:File)=>{
  const URL = window.URL || window.webkitURL;
  const url = URL.createObjectURL(file);
  if(file.type.includes("image")){
    insertImage(url,file)
  }
  else{
    const fileSuffix = file.name.substring(file.name.lastIndexOf("."));
    if (acceptList[1].includes(fileSuffix.toLowerCase())) {
      const fileType = [
        [".doc", ".docx"],
        [".xlsx", ".xls", ".csv"],
        [".zip", ".rar", ".7z"],
        [".txt", ".md"],
        [".pdf"],
      ];
      const index = fileType.findIndex((item) =>
        item.includes(fileSuffix.toLowerCase())
      );
      if (~index) {
        const img = showFileImageList[index];
        insertFiles(img,file)
      } else {
        emits("error", "暂不支持该文件类型进行该操作")
      }
    }
    // 视频文件
    if (acceptList[2].includes(fileSuffix.toLowerCase())) {
      const img = showFileImageList[5];
      insertFiles(img,file,true)
    }
  }
}
// 插入文件地址
const insertUrl=(url:string,type:'img'|'file'|'aov'='img')=>{
  console.log(url,type)
}

// 处理粘贴
const handleParse = async (e: Event) => {
  e.preventDefault();
  try{
    const result = (await onPaste(e)) as string;
    if (result) {
      document.execCommand("insertText", false, result);
    }
  }catch(err){
    emits("error",err)
  }
};

// 粘贴
const onPaste = (e: any) => {
  if (!(e.clipboardData && e.clipboardData.items)) {
    return;
  }
  return new Promise((resolve, reject) => {
    for (let i = 0, len = e.clipboardData.items.length; i < len; i++) {
      const item = e.clipboardData.items[i];
      if (item.kind === "string") {
        item.getAsString((str: string) => {
          resolve(str);
        });
      } else if (item.kind === "file") {
        handleFileAndImageInsert(item);
      } else {
        reject(new Error("不允许复制这种类型!"));
      }
    }
  });
};

// 处理图片和文件在input框中的显示逻辑
const handleFileAndImageInsert = (item: any) => {
  const file = item.getAsFile();
  const isLt8M = file.size / 1024 / 1024 < 8;
  if (!isLt8M) {
    emits("error",'文件不能大于8M')
    return;
  }
  insertFile(file)
};

// 阻止默认事件
const handleAllowDrop = (e: Event) => {
  e.preventDefault();
};

// 放置
const handleDrop = (e: any) => {
  e.preventDefault();
  const copyItems = e.dataTransfer.items;
  for (let i = 0; i < copyItems.length; i++) {
    // 字符串
    if (copyItems[i].kind === "string") {
      if (e.dataTransfer.effectAllowed === "copy") {
        copyItems[i].getAsString((str: string) => {
          insertContent(str);
        });
      }
    }
    // 文件
    if (copyItems[i].kind === "file") {
      handleFileAndImageInsert(copyItems[i]);
    }
  }
};

const {insertImage,insertFile:insertFiles,insertContent,exportMsgData} =useInput({
  editorKey: "chat-input-next-editor",
})


// @功能
// @列表是否显示
const atPopoverVisible = ref(false);

// popver的ref
const atPopoverRef=ref<any>(null)

// 点击popver外部隐藏
const handlePopoverClick=(e:Event)=>{
  const target = e.target as HTMLElement;
  const isSelf = document.getElementById('at-popover')?.contains(target);
  if(!isSelf){
    atPopoverVisible.value=false
  }
}
// 监听@列表显示时的点击事件
watch(()=>atPopoverVisible.value,(newVal)=>{
  if(newVal){
    document.addEventListener('click',handlePopoverClick)
  }
  else{
    document.removeEventListener('click',handlePopoverClick)
  }
},{immediate:true})

// 去除默认事件
const handleKeyDown=(e:KeyboardEvent)=>{
  const reason=e.keyCode===13&&!e.shiftKey||e.keyCode===40||e.keyCode===38
  if(reason){
    e.preventDefault()
  }
}

// 处理@确定事件
const handleAtConfirm=(name:string)=>{
  const newAtEle = document.createElement("a");
  newAtEle.setAttribute("contenteditable", "false");
  newAtEle.setAttribute("style", "user-select: all");
  newAtEle.setAttribute("nickName", name);
  newAtEle.innerText = `@${name} `;
  insertContent(newAtEle.outerHTML,atStr.value)
  atPopoverVisible.value=false
}

const chatInputNext=ref<HTMLElement|null>(null)
// 保持输入框滚动条在最下方
const keepScrollBottom = throttle(
  () => {
    const scrollEle=chatInputNext.value
    if (scrollEle) {
      scrollEle.scrollTop = scrollEle?.scrollHeight;
    }
  },
  200,
  {
    leading: false,
    trailing: true,
  }
);

// @的字符串
const atStr = ref("");

// 搜索出来的@列表
const searchAtList = ref<Array<AtEachMember>>([]);

// 搜索内容
const searchFun=(searchStr:string)=>{
  const pinyinSearchList = new PinyinEngine(props.atUserList, [
    "name",
  ]); 
  // 拼音搜索
  const newList = pinyinSearchList.query(searchStr);
  searchAtList.value = newList;
  if (searchStr && newList.length !== 0) {
    atPopoverVisible.value=true
  } else if (newList.length === 0) {
    atPopoverVisible.value = false;
  }
}

// 处理input事件
const handleInput=(e:Event)=>{
  // 保持输入框滚动条在最底部
  keepScrollBottom();
  const inputE=e as InputEvent
  const sel=window.getSelection() as Selection
  // 如果没有内容则隐藏@列表
  if (inputE.data !== null && !inputE.data.trim()) {
    atPopoverVisible.value = false;
  }
  else{
    const inputEle = e.target as any;
    let searchStr = "";
    let range = sel.getRangeAt(0); // 获取当前光标
    let isOnlyAt=false
    // 如果当前光标所在位置的节点类型是文本节点，且文本内容包含@符号，则获取@符号后面的文本就是搜索的内容
    for (let i = 0; i < inputEle.childNodes.length; i++) {
      const curEle = inputEle.childNodes[i];
      if (curEle.nodeName === "#text" && curEle.nodeValue.includes("@")) {
        const splitArr = curEle.nodeValue
          .substring(0, range.endOffset)
          .split("@");
        searchStr = splitArr[splitArr.length - 1];
        if (!searchStr || !searchStr.length) {
          isOnlyAt = true;
        }
      }
    }
    atStr.value = searchStr;
    if (
      inputE.inputType === "insertText" ||
      inputE.inputType === "insertCompositionText"
    ){
      // 输入字符
      if (inputE.data === "@") {
        atPopoverVisible.value=true
      }else{
        searchFun(searchStr)
      }
    }else if (inputE.inputType === "deleteContentBackward") {
      const previousNode = range.startContainer.childNodes[range.startOffset - 1];
      if(previousNode&&previousNode.nodeName==='A'){
        e.preventDefault();
        // 删除整个 <a> 节点
        previousNode.remove();
      }
      //删除字符
      else if (!searchStr&&!isOnlyAt) {
        atPopoverVisible.value = false;
      } else {
        searchFun(searchStr)
      }
    }
  }
}

// 导出数据
const exportData=()=>{
  const retrunData=exportMsgData()
  if(retrunData){
    const {msgData,needUploadFile,atUserList}=retrunData
    return {
      msgData,
      needUploadFile,
      atUserList
    }
  }
  return;
}

defineExpose({
  insertFile,
  insertUrl,
  exportData
})
</script>
<style scoped>
.chat-input-next-wrap{
  position: relative;
  box-sizing: border-box;
}
.chat-input-next{
  width:500px;
  height:200px;
  max-height: 200px;
  overflow-y: auto;
  padding:5px;
}

.chat-input-next::-webkit-scrollbar {
  display: none;
}

[contenteditable]:empty:before {
  content: attr(placeholder);
  color: #ccc;
}
[contenteditable] {
  outline: 0px solid transparent;
}
</style>